package view;

import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;

import model.Arena;
import controler.InputHandler;

public class MouseInputHandler3D implements MouseListener, MouseMotionListener,
		MouseWheelListener {

	private static MouseInputHandler3D instance = null;

	private int prevMouseX, prevMouseY;
	private boolean mouseRButtonDown = false;

	private Camera camera;
	private double aspect;
	private double width;
	private double height;

	/**
	 * Construtor da classe MouseInputHandler. Armazena em instance a
	 * inst�ncia da classe MouseInputHandler, para que exista apenas uma
	 * refer�ncia.
	 */
	public MouseInputHandler3D() {
		MouseInputHandler3D.instance = this;
	}

	/**
	 * M�todo de acesso � instancia da classe MouseInputHandler. esse
	 * m�todo deve ser chamado depois da inicializacao da inst�ncia
	 * atrav�s do construtor
	 * 
	 * @return Uma inst�ncia da classe MouseInputHandler.
	 */
	public static MouseInputHandler3D instanceOf() {
		return instance;
	}

	public void mouseEntered(MouseEvent e) {
	}

	/**
	 * M�todo para o tratamento de quando um botao do mouse � apertado, ele
	 * guarda se o bot�o direito do mouse est� apertado, para possibilitar
	 * um tratamento diferente caso ele esteja.
	 */
	public void mousePressed(MouseEvent e) {
		prevMouseX = e.getX();
		prevMouseY = e.getY();
		if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
			mouseRButtonDown = true;
		}
	}

	/**
	 * M�todo para o tratamento de quando um botao do mouse � solto, ele
	 * guarda se o bot�o direito do mouse deixou de ser apertado, para
	 * possibilitar um tratamento diferente caso ele esteja.
	 */
	public void mouseReleased(MouseEvent e) {
		if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
			mouseRButtonDown = false;
		}
	}

	/**
	 * M�todo para o tratamento de quando o wheel do mouse � movido. Ele
	 * chama uma fun��o da c�mera que altera o zoom atual.
	 */
	public void mouseWheelMoved(MouseWheelEvent e) {
		if (camera != null)
			camera.changeZoom(e.getWheelRotation());
	}

	/**
	 * M�todo para o tratamento de quando um clique � feito na tela, ele
	 * trasnforma o ponto x,y da coordenada onde o usu�rio clicou na tela para
	 * uma trajetoria em espa�o 3D e faz a intersec��o dessa trajet�ria
	 * com o plano z=0, que � o ch�o da nossa aplica��o. Ao encontrar a
	 * posi��o onde o usu�rio clicou no ch�o ele chama o InputHandler
	 * para lidar com esse comando.
	 */
	public void mouseClicked(MouseEvent e) {
		int x = e.getX(); // Coordenadas x,y na tela
		int y = e.getY();

		// FatorX e Y sao valores de [-0.5, 0.5] que indicaram quanto "andar" em
		// cada direcao no espaco 3D
		double fatorX, fatorY;
		fatorX = (double) x / width; // por enquanto vale de 0 a 1 os vetorer
		fatorY = 1 - (double) y / height; // subtraimos 0.5 pois a origem no
		// JOGL esta no meio da tela
		fatorX -= 0.5;
		fatorY -= 0.5;

		// dist sera usado para calcular o tamanho dos vetores de direcao e
		// torna-los unitarios
		double dist;

		// vetor indo para dentro da tela, onde:
		// 0 equivale a coordenada x; posicao 1 e a coordenada y e posicao 2 e a
		// z
		// Ele e o vetor do foco da camera (eye) menos a posicao da camera
		double[] zvet = new double[3];
		zvet[0] = camera.getXeye() - camera.getXpos();
		zvet[1] = camera.getYeye() - camera.getYpos();
		zvet[2] = camera.getZeye() - camera.getZpos();
		// dist recebe comprimento do vetor zvet e depois e usado para tornar
		// zvet unitario.
		dist = Math.sqrt(zvet[0] * zvet[0] + zvet[1] * zvet[1] + zvet[2]
				* zvet[2]);
		for (int i = 0; i < 3; i++)
			zvet[i] = zvet[i] / dist;

		// vetor indo para direita da tela, em espaco 3D, ortogonal ao que entra
		// na tela (zvet)
		// Ele sera calculado como o produto vetorial de zvet com (0,0,1)
		double[] xvet = new double[3];
		xvet[0] = zvet[1];
		xvet[1] = -zvet[0];
		xvet[2] = 0;
		// normaliza-se o xvet:
		dist = Math.sqrt(xvet[0] * xvet[0] + xvet[1] * xvet[1] + xvet[2]
				* xvet[2]);
		for (int i = 0; i < 3; i++)
			xvet[i] = xvet[i] / dist;

		// finalmente o vetor yvet esta indo para cima da tela, em espaco 3D,
		// ortogonal a xvet e zvet.
		// Ele sera calculado como produto vetorial de zvet com xvet
		double[] yvet = new double[3];
		yvet[0] = zvet[1] * xvet[2] - zvet[2] * xvet[1];
		yvet[1] = zvet[2] * xvet[0] - zvet[0] * xvet[2];
		yvet[2] = zvet[0] * xvet[1] - zvet[1] * xvet[0];
		dist = Math.sqrt(yvet[0] * yvet[0] + yvet[1] * yvet[1] + yvet[2]
				* yvet[2]);
		for (int i = 0; i < 3; i++)
			// transforma em unitario e inverte para apontar para cima na tela
			yvet[i] = -yvet[i] / dist;

		// Esses valores sao as alturas e larguras dos planos de visualizacao do
		// gluPerspective
		// sao calculados usando a relacao de aspecto, o zoom (angulo de
		// abertura) e a distancia ate a camera.
		double heightNear, widthNear, heightFar, widthFar;
		heightNear = 4 * Math.tan(Math.toRadians(camera.getZoom() / 2)); // distancia
		// da
		// camera
		// ate
		// o
		// plano
		// da
		// frente
		// e
		// 2
		heightFar = 400 * Math.tan(Math.toRadians(camera.getZoom() / 2)); // distancia
		// ate
		// o
		// plano
		// de
		// traz
		// e
		// 200
		widthNear = aspect * heightNear; // aspect equivale a width/height
		widthFar = aspect * heightFar;

		// Ponto na reta clicada que esta sobre o plano de clipping mais proximo
		// da camera,
		// Sera calculado andando fatorX no vetor direcao xvet e fatorY no vetor
		// direcao yvet e 2 na direcao zvet
		double[] beginOfClick = new double[3];
		beginOfClick[0] = camera.getXpos();
		beginOfClick[1] = camera.getYpos(); // Comeca na posicao da camera
		beginOfClick[2] = camera.getZpos();
		// Anda 2 na direcao zvet:
		beginOfClick[0] += 2 * zvet[0];
		beginOfClick[1] += 2 * zvet[1];
		beginOfClick[2] += 2 * zvet[2];
		// Anda fatorX na direcao xvet no plano near:
		beginOfClick[0] += fatorX * widthNear * xvet[0];
		beginOfClick[1] += fatorX * widthNear * xvet[1];
		beginOfClick[2] += fatorX * widthNear * xvet[2];
		// Anda fatorY na direcao yvet no plano near:
		beginOfClick[0] += fatorY * heightNear * yvet[0];
		beginOfClick[1] += fatorY * heightNear * yvet[1];
		beginOfClick[2] += fatorY * heightNear * yvet[2];

		// Ponto na reta clicada que esta sobre o plano de clipping mais longe
		// da camera,
		// Sera calculado andando fatorX no vetor direcao xvet e fatorY no vetor
		// direcao yvet e 200 no zvet
		double[] endOfClick = new double[3];
		endOfClick[0] = camera.getXpos();
		endOfClick[1] = camera.getYpos(); // Comeca na posicao da camera
		endOfClick[2] = camera.getZpos();
		// Anda 200 na direcao zvet:
		endOfClick[0] += 200 * zvet[0];
		endOfClick[1] += 200 * zvet[1];
		endOfClick[2] += 200 * zvet[2];
		// Anda fatorX na direcao xvet no plano far:
		endOfClick[0] += fatorX * widthFar * xvet[0];
		endOfClick[1] += fatorX * widthFar * xvet[1];
		endOfClick[2] += fatorX * widthFar * xvet[2];
		// Anda fatorY na direcao yvet no plano far:
		endOfClick[0] += fatorY * heightFar * yvet[0];
		endOfClick[1] += fatorY * heightFar * yvet[1];
		endOfClick[2] += fatorY * heightFar * yvet[2];

		// Com isso temos os dois pontos que definem a trajetoria do clique na
		// tela,
		// como se clicar fosse atirar uma flecha para dentro do espaco 3D,
		// esses pontos definem a trajetoria dela

		// Se o ponto do final esta abaixo do z=0 e o do comeco esta acima a
		// flecha atravessa o plano z=0
		if (beginOfClick[2] > 0 && endOfClick[2] < 0) {
			double[] directionOfClick = new double[3];
			directionOfClick[0] = endOfClick[0] - beginOfClick[0];
			directionOfClick[1] = endOfClick[1] - beginOfClick[1];
			directionOfClick[2] = endOfClick[2] - beginOfClick[2];
			dist = Math.sqrt(directionOfClick[0] * directionOfClick[0]
					+ directionOfClick[1] * directionOfClick[1]
					+ directionOfClick[2] * directionOfClick[2]);
			for (int i = 0; i < 3; i++)
				directionOfClick[i] = directionOfClick[i] / dist;

			double gamma; // Gamma e a quantidade necessaria de se andar por
			// direction para atingir o plano z=0.
			gamma = beginOfClick[2] / directionOfClick[2];

			double groundX, groundY; // Valor onde a trajetoria se intercepta
			// com z=0;
			groundX = beginOfClick[0] - gamma * directionOfClick[0];
			groundY = beginOfClick[1] - gamma * directionOfClick[1]; // anda
			if(!GLComponent3Dview.isValidPosition(groundX, groundY)){
				return;
			}
			// gamma
			// na
			// direcao
			// do
			// click,
			// chegando
			// no
			// plano
			// z=0;

			if (e.getClickCount() == 1) {
				InputHandler.instanceOf().oneClickOn(groundX, groundY);
			} else if (e.getClickCount() == 2) {
				InputHandler.instanceOf().twoClickOn(groundX, groundY);
			} else if (e.getClickCount() == 3) {
				InputHandler.instanceOf().threeClickOn(groundX, groundY);
			}

			System.out.println("GroudX = " + groundX);
			if (!GLComponent3Dview.isValidPosition(groundX, groundY)) {
				return;
			}
			System.out.println("GroudY = " + groundY);
			if (e.getClickCount() == 1) {
				InputHandler.instanceOf().oneClickOn(groundX, groundY);
			} else if (e.getClickCount() == 2) {
				InputHandler.instanceOf().twoClickOn(groundX, groundY);
			} else if (e.getClickCount() == 3) {
				InputHandler.instanceOf().threeClickOn(groundX, groundY);
			}
		}

	}

	/**
	 * M�todo para o tratamento de quando o mouse � movido com um dos seus
	 * bot�es pressionado, nesse caso o m�todo calcula quanto o mouse foi
	 * arrastado e manda isso como um comando para rotacionar para a c�mera
	 */
	public void mouseDragged(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		Dimension size = e.getComponent().getSize();

		float thetaX = 360.0f * ((float) (x - prevMouseX) / (float) size.width);
		float thetaY = 360.0f * ((float) (prevMouseY - y) / (float) size.height);

		prevMouseX = x;
		prevMouseY = y;

		/*
		 * a rotacao pode ser feita com o botao direito ou esquerdo, para
		 * implementar outra funcionalidade no botao esquerdo basta trocar as
		 * chamadas feitas abaixo para outras que fazem o tratamento.
		 */
		if (camera != null && !mouseRButtonDown) {
			camera.rotateZ(thetaX);
			camera.lowerCamera(thetaY);

		} else if (camera != null && mouseRButtonDown) {
			camera.rotateZ(thetaX);
			camera.lowerCamera(thetaY);
		}

	}

	/**
	 * M�todo para o tratamento de quando o mouse � movido, no momento n�o
	 * fazemos tratamento algum
	 */
	public void mouseMoved(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		Dimension size = e.getComponent().getSize();
		float thetaX = 360.0f * ((float) (x - prevMouseX) / (float) size.width);
		float thetaY = 360.0f * ((float) (prevMouseY - y) / (float) size.height);

		prevMouseX = x;
		prevMouseY = y;

		if (!Arena.instanceOf().getSelected().isMoving()) {
			double fatorX, fatorY;
			fatorX = (double) x / width; // por enquanto vale de 0 a 1 os
			// vetorer
			fatorY = 1 - (double) y / height; // subtraimos 0.5 pois a origem no
			// JOGL esta no meio da tela
			fatorX -= 0.5;
			fatorY -= 0.5;

			double heightNear, widthNear, heightFar, widthFar;
			heightNear = 4 * Math.tan(Math.toRadians(camera.getZoom() / 2));
			heightFar = 400 * Math.tan(Math.toRadians(camera.getZoom() / 2));
			widthNear = aspect * heightNear; // aspect equivale a width/height
			widthFar = aspect * heightFar;

			double dist;
		}
	}

	/**
	 * setCamera � usado para informar o MouseInputHandler com que c�mera
	 * ele deve se comunicar em rela��o a eventos de controle de c�mera,
	 * como rota��o ou mudan�a de zoom.
	 */
	public void setCamera(Camera cam) {
		camera = cam;
	}

	/**
	 * setWindowSize informa o MouseInputHandler do tamanho da tela onde a cena
	 * est� sendo desenhada, essa informa��o � vital para o tratamento
	 * adequado de cliques na tela.
	 */
	public void setWindowSize(int Width, int Height) {
		aspect = (double) Width / Height;
		width = Width;
		height = Height;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
	 */
	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}
}
